This notebook contains the code samples found in Chapter 3, Section 5 of Deep Learning with R. Note that the original text features far more content, in particular further explanations and figures: in this notebook, you will only find source code and related comments.


Data Exploration & Preparation

Attribute Name Explanation Remarks
ID Client number
CODE_GENDER Gender
FLAG_OWN_CAR Is there a car
FLAG_OWN_REALTY Is there a property
CNT_CHILDREN Number of children
AMT_INCOME_TOTAL Annual income
NAME_INCOME_TYPE Income category
NAME_EDUCATION_TYPE Education level
NAME_FAMILY_STATUS Marital status
NAME_HOUSING_TYPE Way of living
DAYS_BIRTH Birthday Count backwards from current day (0), -1 means yesterday
DAYS_EMPLOYED Start date of employment Count backwards from current day(0). If positive, it means the person unemployed.
FLAG_MOBIL Is there a mobile phone
FLAG_WORK_PHONE Is there a work phone
FLAG_PHONE Is there a phone
FLAG_EMAIL Is there an email
OCCUPATION_TYPE Occupation
CNT_FAM_MEMBERS Family size

Main task


Some hints


Important notes


Data import

#install.packages("tidymodels")
#install.packages("themis")
library(here)
library(tidyverse)
library(ggplot2)
library(dplyr)
library(tensorflow)
library(tfdatasets)
library(tidymodels)
library(keras)
library(caret)
library(themis)
#LOAD DATA
setwd(getwd())
dataIn = "../Data/Dataset-part-2.csv"
data_in <- read.csv(dataIn,header = TRUE, sep =',')
#View(data_in)
data <- data.frame(data_in)
summary(data)
       ID          CODE_GENDER        FLAG_OWN_CAR       FLAG_OWN_REALTY     CNT_CHILDREN     AMT_INCOME_TOTAL 
 Min.   :5008804   Length:67614       Length:67614       Length:67614       Min.   : 0.0000   Min.   :  26100  
 1st Qu.:5465941   Class :character   Class :character   Class :character   1st Qu.: 0.0000   1st Qu.: 112500  
 Median :5954270   Mode  :character   Mode  :character   Mode  :character   Median : 0.0000   Median : 157500  
 Mean   :5908133                                                            Mean   : 0.4206   Mean   : 178867  
 3rd Qu.:6289080                                                            3rd Qu.: 1.0000   3rd Qu.: 225000  
 Max.   :7965248                                                            Max.   :19.0000   Max.   :6750000  
 NAME_INCOME_TYPE   NAME_EDUCATION_TYPE NAME_FAMILY_STATUS NAME_HOUSING_TYPE    DAYS_BIRTH     DAYS_EMPLOYED   
 Length:67614       Length:67614        Length:67614       Length:67614       Min.   :-25201   Min.   :-17531  
 Class :character   Class :character    Class :character   Class :character   1st Qu.:-19438   1st Qu.: -2886  
 Mode  :character   Mode  :character    Mode  :character   Mode  :character   Median :-15592   Median : -1305  
                                                                              Mean   :-15914   Mean   : 62022  
                                                                              3rd Qu.:-12347   3rd Qu.:  -321  
                                                                              Max.   : -7489   Max.   :365243  
   FLAG_MOBIL FLAG_WORK_PHONE    FLAG_PHONE       FLAG_EMAIL     OCCUPATION_TYPE    CNT_FAM_MEMBERS 
 Min.   :1    Min.   :0.0000   Min.   :0.0000   Min.   :0.0000   Length:67614       Min.   : 1.000  
 1st Qu.:1    1st Qu.:0.0000   1st Qu.:0.0000   1st Qu.:0.0000   Class :character   1st Qu.: 2.000  
 Median :1    Median :0.0000   Median :0.0000   Median :0.0000   Mode  :character   Median : 2.000  
 Mean   :1    Mean   :0.2028   Mean   :0.2742   Mean   :0.1005                      Mean   : 2.174  
 3rd Qu.:1    3rd Qu.:0.0000   3rd Qu.:1.0000   3rd Qu.:0.0000                      3rd Qu.: 3.000  
 Max.   :1    Max.   :1.0000   Max.   :1.0000   Max.   :1.0000                      Max.   :20.000  
    status         
 Length:67614      
 Class :character  
 Mode  :character  
                   
                   
                   
plot(data$status)

##Cleanup

# Check for duplicates 
sum(duplicated(data))
[1] 0
# No duplicates

#Remove ID (irrelevant) and FLAG_MOBIL (always 1)
data <- data %>% select(-ID, -FLAG_MOBIL)
cols <- c("CODE_GENDER","FLAG_OWN_CAR","FLAG_OWN_REALTY","NAME_INCOME_TYPE","NAME_EDUCATION_TYPE", "NAME_FAMILY_STATUS", "NAME_HOUSING_TYPE","FLAG_WORK_PHONE","FLAG_PHONE","FLAG_EMAIL", "OCCUPATION_TYPE","status")
cols
 [1] "CODE_GENDER"         "FLAG_OWN_CAR"        "FLAG_OWN_REALTY"     "NAME_INCOME_TYPE"   
 [5] "NAME_EDUCATION_TYPE" "NAME_FAMILY_STATUS"  "NAME_HOUSING_TYPE"   "FLAG_WORK_PHONE"    
 [9] "FLAG_PHONE"          "FLAG_EMAIL"          "OCCUPATION_TYPE"     "status"             
data[cols] <- lapply(data[cols],factor)

# Replacing empty values with "Unknown"
levels(data$OCCUPATION_TYPE) <- c(levels(data$OCCUPATION_TYPE), "Unknown")
data$OCCUPATION_TYPE[is.na(data$OCCUPATION_TYPE)] <- "Unknown"

# Replacing C and X in Status
levels(data$status)[levels(data$status)=="C"] <- "6"
#data$status[data$status == "X"] <- 7
levels(data$status)[levels(data$status)=="X"] <- "7"
# #Convert factors into numericals
# data %<>% mutate_if(is.factor, as.numeric)

summary(data)
 CODE_GENDER FLAG_OWN_CAR FLAG_OWN_REALTY  CNT_CHILDREN     AMT_INCOME_TOTAL              NAME_INCOME_TYPE
 F:43924     N:43107      N:21090         Min.   : 0.0000   Min.   :  26100   Commercial associate:15640  
 M:23690     Y:24507      Y:46524         1st Qu.: 0.0000   1st Qu.: 112500   Pensioner           :11982  
                                          Median : 0.0000   Median : 157500   State servant       : 5044  
                                          Mean   : 0.4206   Mean   : 178867   Student             :    4  
                                          3rd Qu.: 1.0000   3rd Qu.: 225000   Working             :34944  
                                          Max.   :19.0000   Max.   :6750000                               
                                                                                                          
                    NAME_EDUCATION_TYPE            NAME_FAMILY_STATUS           NAME_HOUSING_TYPE
 Academic degree              :   38    Civil marriage      : 6016    Co-op apartment    :  227  
 Higher education             :16890    Married             :44906    House / apartment  :60307  
 Incomplete higher            : 2306    Separated           : 4125    Municipal apartment: 2303  
 Lower secondary              :  716    Single / not married: 9528    Office apartment   :  587  
 Secondary / secondary special:47664    Widow               : 3039    Rented apartment   : 1020  
                                                                      With parents       : 3170  
                                                                                                 
   DAYS_BIRTH     DAYS_EMPLOYED    FLAG_WORK_PHONE FLAG_PHONE FLAG_EMAIL    OCCUPATION_TYPE  CNT_FAM_MEMBERS 
 Min.   :-25201   Min.   :-17531   0:53904         0:49071    0:60819    Unknown    :20699   Min.   : 1.000  
 1st Qu.:-19438   1st Qu.: -2886   1:13710         1:18543    1: 6795    Laborers   :12425   1st Qu.: 2.000  
 Median :-15592   Median : -1305                                         Sales staff: 6899   Median : 2.000  
 Mean   :-15914   Mean   : 62022                                         Core staff : 6059   Mean   : 2.174  
 3rd Qu.:-12347   3rd Qu.:  -321                                         Managers   : 4950   3rd Qu.: 3.000  
 Max.   : -7489   Max.   :365243                                         Drivers    : 4427   Max.   :20.000  
                                                                         (Other)    :12155                   
     status     
 0      :52133  
 1      : 6491  
 7      : 5790  
 6      : 1805  
 2      :  712  
 5      :  374  
 (Other):  309  

Preprocessing

set.seed(1)
trainIndex <- initial_split(data, prop = 0.8, strata = status) 
trainingSet <- training(trainIndex)
testSet <- testing(trainIndex)
status_folds <- vfold_cv(trainingSet, v = 10, strata = status)
status_folds
#  10-fold cross-validation using stratification 
# Remove outliers (Out of 1.5x Interquartile Range)
# CNT_CHILDREN
boxplot(trainingSet$CNT_CHILDREN, horizontal=TRUE, main="CNT_CHILDREN")

Q1_Child <- quantile(trainingSet$CNT_CHILDREN, .25)
Q3_Child <- quantile(trainingSet$CNT_CHILDREN, .75)
IQR_Child <- IQR(trainingSet$CNT_CHILDREN)
# Now we keep the values within 1.5*IQR of Q1 and Q3
trainingSet <- subset(trainingSet, trainingSet$CNT_CHILDREN > (Q1_Child - 1.5*IQR_Child) & trainingSet$CNT_CHILDREN < (Q3_Child + 1.5*IQR_Child))
dim(trainingSet)
[1] 53330    17
# AMT_INCOME_TOTAL
boxplot(trainingSet$AMT_INCOME_TOTAL, horizontal=TRUE, main="AMT_INCOME_TOTAL")

Q1_AIT <- quantile(trainingSet$AMT_INCOME_TOTAL, .25)
Q3_AIT <- quantile(trainingSet$AMT_INCOME_TOTAL, .75)
IQR_AIT <- IQR(trainingSet$AMT_INCOME_TOTAL)
# Now we keep the values within 1.5*IQR of Q1 and Q3
trainingSet <- subset(trainingSet, trainingSet$AMT_INCOME_TOTAL > (Q1_AIT - 1.5*IQR_AIT) & trainingSet$AMT_INCOME_TOTAL < (Q3_AIT + 1.5*IQR_AIT))
dim(trainingSet)
[1] 51748    17
set.seed(5)
preprocRecipe <-
  recipe(status ~., data = data) %>%
  step_dummy(all_nominal(), -status,  one_hot = TRUE) %>%
  step_range(all_predictors(), -all_nominal(), min = 0, max = 1)%>%
 # step_downsample(status, over_ratio = 1) %>%
  step_smote(status, over_ratio = 1, skip=TRUE) %>%
 # step_smotenc(status, over_ratio = 1) %>%
 #step_adasyn(status, over_ratio = 1) %>%
 #step_nearmiss(status, over_ratio = 1) %>%
   
  step_dummy(status,  one_hot = TRUE)# %>%

In this step the above defined receipt is extracted using the prep() function, and then use the bake() function to transform a set of data based on that recipe.

# retain = TRUE and new_data = NULL ensures that pre-processed trainingSet is returned 
trainingSet_processed <- preprocRecipe %>%
  prep(trainingSet, retain = TRUE) %>%
  bake(new_data = NULL)
testSet_processed <- preprocRecipe %>%
  prep(testSet) %>%
  bake(new_data =testSet)

#summary(trainingSet_processed)

Check data

# summarize the class distribution
percentage <- 100-prop.table(table(data$status)) * 100
cbind(freq=table(data$status), percentage=percentage)
class_weights <- list("0"=1,"1"=100)

# Turn data frame into data matrix
matrix_data <- trainingSet_processed %>% select(-tail(names(trainingSet_processed), 8))
matrix_targets <- trainingSet_processed %>% select(tail(names(trainingSet_processed), 8))

matrix_data_test  <- testSet_processed %>% select(-tail(names(testSet_processed), 8))
matrix_targets_test  <- testSet_processed %>% select(tail(names(testSet_processed), 8))

#Subset only 100 entries for testing
#matrix_data <- matrix_data[1:100, ]
#matrix_targets <- matrix_targets[1:100, ]

Build Model

#train_data <- matrix_data
train_data <- data.matrix(matrix_data)
test_data <- data.matrix(matrix_data_test)
train_targets <- data.matrix(matrix_targets)
test_targets <- data.matrix(matrix_targets_test)

# Function to build the model
build_model <- function() {
  model <- keras_model_sequential() %>%
    #layer_batch_normalization(axis = -1L, input_shape = dim(train_data)[[2]]) %>%
    layer_dense(units = 64, activation = "relu", input_shape = dim(train_data)[[2]]) %>%
    layer_dropout(0.3) %>%
    layer_dense(units = 64, activation = "relu") %>%
    layer_dropout(0.3) %>%
    layer_dense(units = 8, activation = "softmax") 

  model %>% compile(
    optimizer = optimizer_sgd(learning_rate = 0.2),
    loss = "categorical_crossentropy",
    metrics = "categorical_accuracy"
  )

}

K-Fold-Validation

# mean <- apply(matrix_data, 2, mean)
# std <- apply(matrix_data, 2, sd)
# train_data <- scale(matrix_data, center = mean, scale = std)
# test_data <- scale(matrix_data, center = mean, scale = std)
# train_targets <- matrix_targets


k <- 10
indices <- sample(1:nrow(train_data))
folds <- cut(indices, breaks = k, labels = FALSE)

num_epochs <- 1500
all_acc_histories <- NULL
for (i in 1:k) {
  cat("processing fold #", i, "\n")

  val_indices <- which(folds == i, arr.ind = TRUE)
  val_data <- train_data[val_indices,] #test_data#
  val_targets <- train_targets[val_indices,] #test_targets#
  
  partial_train_data <- train_data[-val_indices,]
  partial_train_targets <- train_targets[-val_indices,]
  model <- build_model()

  # Train the model (in silent mode, verbose=0)
  # Batch size https://stats.stackexchange.com/questions/153531/what-is-batch-size-in-neural-network
  # One epoch = one forward pass and one backward pass of all the training examples
  # Batch size = the number of training examples in one forward/backward pass. The higher the batch size, the more memory space you'll need.
  # Number of iterations = number of passes, each pass using [batch size] number of examples. To be clear, one pass = one forward pass + one backward pass (we do not count the forward pass and backward pass as two different passes).
  # Batch size 32 much faster than 1, also the smaller the batch the less accurate the estimate of the gradient will be.
  history <- model %>% fit(
    partial_train_data, partial_train_targets,
    validation_data = list(val_data, val_targets),
    epochs = num_epochs, batch_size = 128, verbose = 2, class_weights = percentage
  )
  acc_history <- history$metrics$val_categorical_accuracy
  all_acc_histories <- rbind(all_acc_histories, acc_history)
}


#reticulate::py_last_error()

#We can then compute the average of the per-epoch ACC scores for all folds:

average_acc_history <- data.frame(
  epoch = seq(1:ncol(all_acc_histories)),
  validation_acc = apply(all_acc_histories, 2, mean)
)


max(average_acc_history$validation_acc)

library(ggplot2)
ggplot(average_acc_history, aes(x = epoch, y = validation_acc)) + geom_line()

#It may be a bit hard to see the plot due to scaling issues and relatively high variance. Let's use `geom_smooth()` to try to get a clearer picture:
ggplot(average_acc_history, aes(x = epoch, y = validation_acc)) + geom_smooth()

# Evaluate on Testset
eval <- evaluate(model, test_data, test_targets, verbose = 1)
eval

# Save model and history, please change the name
# write.csv(average_acc_history, "../Doc/Versuch 3/Try 3.csv", row.names=FALSE)
# save_model_hdf5(model, "../Doc/Versuch 3/model 3.hfd5", overwrite = TRUE, include_optimizer = TRUE)

# Load model
# Use model_history as precaution
# model_history <- load_model_hdf5("../Doc/Versuch 3/model 3.hfd5", custom_objects = NULL, compile = TRUE)
LS0tDQp0aXRsZTogIlByb2plY3QgUGFydCAyIg0Kb3V0cHV0OiANCiAgaHRtbF9ub3RlYm9vazogDQogICAgdGhlbWU6IGNlcnVsZWFuDQogICAgaGlnaGxpZ2h0OiB0ZXh0bWF0ZQ0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFKQ0KYGBgDQoNCioqKg0KDQpUaGlzIG5vdGVib29rIGNvbnRhaW5zIHRoZSBjb2RlIHNhbXBsZXMgZm91bmQgaW4gQ2hhcHRlciAzLCBTZWN0aW9uIDUgb2YgW0RlZXAgTGVhcm5pbmcgd2l0aCBSXShodHRwczovL3d3dy5tYW5uaW5nLmNvbS9ib29rcy9kZWVwLWxlYXJuaW5nLXdpdGgtcikuIE5vdGUgdGhhdCB0aGUgb3JpZ2luYWwgdGV4dCBmZWF0dXJlcyBmYXIgbW9yZSBjb250ZW50LCBpbiBwYXJ0aWN1bGFyIGZ1cnRoZXIgZXhwbGFuYXRpb25zIGFuZCBmaWd1cmVzOiBpbiB0aGlzIG5vdGVib29rLCB5b3Ugd2lsbCBvbmx5IGZpbmQgc291cmNlIGNvZGUgYW5kIHJlbGF0ZWQgY29tbWVudHMuDQoNCioqKg0KDQojIERhdGEgRXhwbG9yYXRpb24gJiBQcmVwYXJhdGlvbiANCiogT3VyIGdvYWwgaW4gdGhlIHNlY29uZCBwYXJ0IG9mIHRoZSBhc3NpZ25tZW50IGlzIHRvIHByZWRpY3QgaG93IGdvb2QgYSAobmV3KSBjdXN0b21lciB3aWxsIHBheSANCmJhY2sgdGhlaXIgY3JlZGl0IGNhcmQgZGVwdHMuIEluIHRoZSBkYXRhIHNldCBhcHBsaWNhdGlvbiBkYXRhIGZyb20gY3VycmVudCBjdXN0b21lcnMgKHRoZSBmaXJzdCAxOCANCmF0dHJpYnV0ZXMpIHRvZ2V0aGVyIHdpdGggdGhlaXIgc3RhdHVzIChsYXN0IGF0dHJpYnV0ZTsgdGFyZ2V0KSBhcmUgZ2l2ZW4uICANCiogVGhlIGF0dHJpYnV0ZXMgZnJvbSB0aGUgYXBwbGljYXRpb25zIGFyZSANCg0KQXR0cmlidXRlIE5hbWUgfCBFeHBsYW5hdGlvbiB8IFJlbWFya3MNCi0tLS0tLS0tLS0tLS0gfCAtLS0tLS0tLS0tLS0tIHwgLS0tLS0tLS0tLS0tLQ0KSUQgfCBDbGllbnQgfCBudW1iZXIgDQpDT0RFX0dFTkRFUiB8IEdlbmRlciB8IA0KRkxBR19PV05fQ0FSIHwgSXMgdGhlcmUgYSBjYXIgfCANCkZMQUdfT1dOX1JFQUxUWSB8IElzIHRoZXJlIGEgcHJvcGVydHkgfCANCkNOVF9DSElMRFJFTiB8IE51bWJlciBvZiBjaGlsZHJlbiB8IA0KQU1UX0lOQ09NRV9UT1RBTCB8IEFubnVhbCBpbmNvbWUgfCANCk5BTUVfSU5DT01FX1RZUEUgfCBJbmNvbWUgY2F0ZWdvcnkgfCANCk5BTUVfRURVQ0FUSU9OX1RZUEUgfCBFZHVjYXRpb24gbGV2ZWwgfCANCk5BTUVfRkFNSUxZX1NUQVRVUyB8IE1hcml0YWwgc3RhdHVzIHwgDQpOQU1FX0hPVVNJTkdfVFlQRSB8IFdheSBvZiBsaXZpbmcgfCANCkRBWVNfQklSVEggfCBCaXJ0aGRheSB8IENvdW50IGJhY2t3YXJkcyBmcm9tIGN1cnJlbnQgZGF5ICgwKSwgLTEgbWVhbnMgeWVzdGVyZGF5IA0KREFZU19FTVBMT1lFRCB8IFN0YXJ0IGRhdGUgb2YgZW1wbG95bWVudCB8IENvdW50IGJhY2t3YXJkcyBmcm9tIGN1cnJlbnQgZGF5KDApLiBJZiBwb3NpdGl2ZSwgaXQgbWVhbnMgdGhlIHBlcnNvbiB1bmVtcGxveWVkLiANCkZMQUdfTU9CSUwgfCBJcyB0aGVyZSBhIG1vYmlsZSBwaG9uZSB8IA0KRkxBR19XT1JLX1BIT05FIHwgSXMgdGhlcmUgYSB3b3JrIHBob25lIHwgDQpGTEFHX1BIT05FIHwgSXMgdGhlcmUgYSBwaG9uZSB8IA0KRkxBR19FTUFJTCB8IElzIHRoZXJlIGFuIGVtYWlsIHwgDQpPQ0NVUEFUSU9OX1RZUEUgfCBPY2N1cGF0aW9uIHwgDQpDTlRfRkFNX01FTUJFUlMgfCBGYW1pbHkgc2l6ZSB8IA0KDQoqIFRoZSBsYXN0IGF0dHJpYnV0ZSBzdGF0dXMgY29udGFpbnMgdGhlIOKAnHBheS1iYWNrIGJlaGF2aW9y4oCdLCBpLmUuIHdoZW4gZGlkIHRoYXQgY3VzdG9tZXIgcGF5IGJhY2sgDQp0aGVpciBkZXB0czogDQogICsgMDogMS0yOSBkYXlzIHBhc3QgZHVlIA0KICArIDE6IDMwLTU5IGRheXMgcGFzdCBkdWUgDQogICsgMjogNjAtODkgZGF5cyBvdmVyZHVlIA0KICArIDM6IDkwLTExOSBkYXlzIG92ZXJkdWUgDQogICsgNDogMTIwLTE0OSBkYXlzIG92ZXJkdWUgDQogICsgNTogT3ZlcmR1ZSBvciBiYWQgZGVidHMsIHdyaXRlLW9mZnMgZm9yIG1vcmUgdGhhbiAxNTAgZGF5cyANCiAgKyBDOiBwYWlkIG9mZiB0aGF0IG1vbnRoIA0KICArIFg6IE5vIGxvYW4gZm9yIHRoZSBtb250aCANClBsZWFzZSBub3RlOiBXZSBhcmUgbGVhcm5pbmcgb25seSB0aGUgcGF5LWJhY2sgYmVoYXZpb3IuIFRoZSBkZWNpc2lvbiwgaS5lLiBpZiB3ZSBhY2NlcHQgYSBjdXN0b21lciBvciANCm5vdCwgaXMgZG9uZSBpbiBhbm90aGVyIHByb2Nlc3Mgc3RlcCDigJMgbm90IGhlcmUhICANCg0KDQoqKioNCg0KIyBNYWluIHRhc2sgDQoqIERlc2lnbiB5b3VyIG5ldHdvcmsuIFdoeSBkaWQgeW91IHVzZSBhIGZlZWQtZm9yd2FyZCBuZXR3b3JrLCBvciBhIGNvbnZvbHV0aW9uYWwgb3IgcmVjdXJzaXZlIA0KbmV0d29yayDigJMgYW5kIHdoeSBub3Q/ICANCiogVXNlIGstZm9sZCB2YWxpZGF0aW9uICh3aXRoIGsgPSAxMCkgdG8gZmluZCB0aGUgYmVzdCBoeXBlcnBhcmFtZXRlcnMgZm9yIHlvdXIgbmV0d29yay4gDQoqIFVzZSB0aGUgYXZlcmFnZSBvZiB0aGUgYWNjdXJhY3kgdG8gZXZhbHVhdGUgdGhlIHBlcmZvcm1hbmNlIG9mIHlvdXIgdHJhaW5lZCBuZXR3b3JrLiANCiogRmluZCBhIOKAnHJlYXNvbmFibGXigJ0gZ29vZCBtb2RlbC4gQXJndWUgd2h5IHRoYXQgbW9kZWwgaXMgcmVhc29uYWJsZS4gSWYgeW91IGFyZSBub3QgYWJsZSB0byBmaW5kIGEgDQpyZWFzb25hYmxlIGdvb2QgbW9kZWwsIGV4cGxhaW4gd2hhdCB5b3UgYWxsIGRpZCB0byBmaW5kIGEgZ29vZCBtb2RlbCBhbmQgYXJndWUgd2h5IHlvdSB0aGluayANCnRoYXTigJlzIG5vdCBhIGdvb2QgbW9kZWwuICANCiogU2F2ZSB5b3VyIHRyYWluZWQgbmV1cmFsIG5ldHdvcmsgd2l0aCBzYXZlX21vZGVsX2hkZjUuIEFsc28gc2F2ZSB5b3VyIGRhdGEgc2V0cyB5b3UgdXNlZCANCmZvciB0cmFpbmluZywgdGVzdGluZyBhbmQgdmFsaWRhdGlvbi4gDQoNCioqKg0KDQojIFNvbWUgaGludHMgDQoqIERhdGEgcHJlcHJvY2Vzc2luZyBpcyBlYXNpZXIgaGVyZTsgbm8gZmVhdHVyZSBlbmdpbmVlcmluZyBpcyBuZWVkZWQuIA0KKiBZb3UgbWF5IGJlIGFibGUgdG8gcmV1c2UgcGFydHMgb2YgdGhlIGV4ZXJjaXNlcyB3ZSB1c2VkIGluIG91ciBleGFtcGxlcyBkdXJpbmcgbGVjdHVyZXMuIA0KKiBBbGwgaW4tIGFuZCBvdXRwdXQgdmFsdWVzIG5lZWQgdG8gYmUgZmxvYXRpbmcgbnVtYmVycyAob3IgaW50ZWdlcnMgaW4gZXhjZXB0aW9ucykgaW4gdGhlIHJhbmdlIG9mIA0KWzAsMV0uIA0KKiBQbGVhc2Ugbm90ZSB0aGF0IGEgbmV1cmFsIG5ldHdvcmsgZXhwZWN0cyBhIFIgbWF0cml4IG9yIHZlY3Rvciwgbm90IGRhdGEgZnJhbWVzLiBUcmFuc2Zvcm0geW91ciANCmRhdGEgKGUuZy4gYSBkYXRhIGZyYW1lKSBpbnRvIGEgbWF0cml4IHdpdGggZGF0YS5tYXRyaXggaWYgbmVlZGVkLiAgDQoqIFRoZXJlIGFyZSBzb21lIG1vZGVscyB3aGljaCBzaG93IGFuIGFjY3VyYWN5IGhpZ2hlciB0aGFuIDkwJSAoISkgZm9yIHRyYWluaW5nIChhbmQgdGVzdCkgZGF0YSDigJMgDQphZnRlciBsZWFybmluZyBtb3JlIHRoYW4gMTAwMCBlcG9jaHMuIA0KDQoqKioNCg0KIyBJbXBvcnRhbnQgbm90ZXMNCiogU2luZ2xlLWxhYmVsLCBNdWx0aWNsYXNzIGNsYXNzaWZpY2F0aW9uIHByb2JsZW0gb24gcGFnZSA3MyBpbiBbRGVlcCBMZWFybmluZyB3aXRoIFJdKGh0dHBzOi8vd3d3Lm1hbm5pbmcuY29tL2Jvb2tzL2RlZXAtbGVhcm5pbmctd2l0aC1yKQ0KKiBTcGFjZXMgbXVzdCBiZSByZW1vdmVkIGluIGJldHdlZW4gJ2BgYHtyfScgYW5kICdgYGAnLCBlbHNlIGFuIGVycm9yIHdpdGggJzwhLS0gcm5iLXNvdXJjZS1lbmQgLS0+JyB3aWxsIGJlIHByb2R1Y2VkDQoqIEstRm9sZCBWYWxpZGF0aW9uIG9uIHBhZ2UgODNmZiBhbmQgOTRmZiBpbiBbRGVlcCBMZWFybmluZyB3aXRoIFJdKGh0dHBzOi8vd3d3Lm1hbm5pbmcuY29tL2Jvb2tzL2RlZXAtbGVhcm5pbmctd2l0aC1yKQ0KKiBQYWdlIDExMCwgdXNlIExhc3QtTGF5ZXIgYWN0aXZhdGlvbiBzb2Z0bWF4IGFuZCBsb3NzIGZ1bmN0aW9uIGNhdGVnb3JpY2FsX2Nyb3NzZW50cm9weQ0KKiBDb252b2x1dGlvbmFsIG5ldHdvcmsgYXVzZ2VzY2hsb3NzZW4sIHdlaWwgaGF1cHRzw6RjaGxpY2ggUGF0dGVybiByZWNvZ25pdGlvbi9pbWFnZSBjbGFzc2lmaWNhdGlvbg0KKiBSZWN1cnNpdmUgYXVzZ2VzY2hsb3NzZW4sIHdlaWwgaGF1cHRzw6RjaGxpY2ggZsO8ciBUaW1lU2VyaWVzLVZvcmhlcnNhZ2VuIHZlcndlbmRldCwgb2RlciBmw7xyIFZvcmhlcnNhZ2VuDQoqIEZlZWQtRm9yd2FyZCwgd2VpbCBDbGFzc2lmaWNhdGlvbi1UYXNrDQoNCioqKg0KDQojIyBEYXRhIGltcG9ydA0KYGBge3J9DQojaW5zdGFsbC5wYWNrYWdlcygidGlkeW1vZGVscyIpDQojaW5zdGFsbC5wYWNrYWdlcygidGhlbWlzIikNCmxpYnJhcnkoaGVyZSkNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkodGVuc29yZmxvdykNCmxpYnJhcnkodGZkYXRhc2V0cykNCmxpYnJhcnkodGlkeW1vZGVscykNCmxpYnJhcnkoa2VyYXMpDQpsaWJyYXJ5KGNhcmV0KQ0KbGlicmFyeSh0aGVtaXMpDQojTE9BRCBEQVRBDQpzZXR3ZChnZXR3ZCgpKQ0KZGF0YUluID0gIi4uL0RhdGEvRGF0YXNldC1wYXJ0LTIuY3N2Ig0KZGF0YV9pbiA8LSByZWFkLmNzdihkYXRhSW4saGVhZGVyID0gVFJVRSwgc2VwID0nLCcpDQojVmlldyhkYXRhX2luKQ0KZGF0YSA8LSBkYXRhLmZyYW1lKGRhdGFfaW4pDQpzdW1tYXJ5KGRhdGEpDQpwbG90KGRhdGEkc3RhdHVzKQ0KYGBgDQojI0NsZWFudXANCmBgYHtyfQ0KIyBDaGVjayBmb3IgZHVwbGljYXRlcyANCnN1bShkdXBsaWNhdGVkKGRhdGEpKQ0KIyBObyBkdXBsaWNhdGVzDQoNCiNSZW1vdmUgSUQgKGlycmVsZXZhbnQpIGFuZCBGTEFHX01PQklMIChhbHdheXMgMSkNCmRhdGEgPC0gZGF0YSAlPiUgc2VsZWN0KC1JRCwgLUZMQUdfTU9CSUwpDQpjb2xzIDwtIGMoIkNPREVfR0VOREVSIiwiRkxBR19PV05fQ0FSIiwiRkxBR19PV05fUkVBTFRZIiwiTkFNRV9JTkNPTUVfVFlQRSIsIk5BTUVfRURVQ0FUSU9OX1RZUEUiLCAiTkFNRV9GQU1JTFlfU1RBVFVTIiwgIk5BTUVfSE9VU0lOR19UWVBFIiwiRkxBR19XT1JLX1BIT05FIiwiRkxBR19QSE9ORSIsIkZMQUdfRU1BSUwiLCAiT0NDVVBBVElPTl9UWVBFIiwic3RhdHVzIikNCmNvbHMNCmRhdGFbY29sc10gPC0gbGFwcGx5KGRhdGFbY29sc10sZmFjdG9yKQ0KDQojIFJlcGxhY2luZyBlbXB0eSB2YWx1ZXMgd2l0aCAiVW5rbm93biINCmxldmVscyhkYXRhJE9DQ1VQQVRJT05fVFlQRSkgPC0gYyhsZXZlbHMoZGF0YSRPQ0NVUEFUSU9OX1RZUEUpLCAiVW5rbm93biIpDQpkYXRhJE9DQ1VQQVRJT05fVFlQRVtpcy5uYShkYXRhJE9DQ1VQQVRJT05fVFlQRSldIDwtICJVbmtub3duIg0KDQojIFJlcGxhY2luZyBDIGFuZCBYIGluIFN0YXR1cw0KbGV2ZWxzKGRhdGEkc3RhdHVzKVtsZXZlbHMoZGF0YSRzdGF0dXMpPT0iQyJdIDwtICI2Ig0KI2RhdGEkc3RhdHVzW2RhdGEkc3RhdHVzID09ICJYIl0gPC0gNw0KbGV2ZWxzKGRhdGEkc3RhdHVzKVtsZXZlbHMoZGF0YSRzdGF0dXMpPT0iWCJdIDwtICI3Ig0KIyAjQ29udmVydCBmYWN0b3JzIGludG8gbnVtZXJpY2Fscw0KIyBkYXRhICU8PiUgbXV0YXRlX2lmKGlzLmZhY3RvciwgYXMubnVtZXJpYykNCg0Kc3VtbWFyeShkYXRhKQ0KYGBgDQoNCiMgUHJlcHJvY2Vzc2luZw0KYGBge3IgQ3JlYXRlIGEgcmVjaXBlIGZvciBwcmVwcm9jfQ0Kc2V0LnNlZWQoMSkNCnRyYWluSW5kZXggPC0gaW5pdGlhbF9zcGxpdChkYXRhLCBwcm9wID0gMC44LCBzdHJhdGEgPSBzdGF0dXMpIA0KdHJhaW5pbmdTZXQgPC0gdHJhaW5pbmcodHJhaW5JbmRleCkNCnRlc3RTZXQgPC0gdGVzdGluZyh0cmFpbkluZGV4KQ0Kc3RhdHVzX2ZvbGRzIDwtIHZmb2xkX2N2KHRyYWluaW5nU2V0LCB2ID0gMTAsIHN0cmF0YSA9IHN0YXR1cykNCnN0YXR1c19mb2xkcw0KYGBgDQpgYGB7cn0NCiMgUmVtb3ZlIG91dGxpZXJzIChPdXQgb2YgMS41eCBJbnRlcnF1YXJ0aWxlIFJhbmdlKSBvbmx5IG9uIHRyYWluaW5nIHNldA0KIyBDTlRfQ0hJTERSRU4NCmJveHBsb3QodHJhaW5pbmdTZXQkQ05UX0NISUxEUkVOLCBob3Jpem9udGFsPVRSVUUsIG1haW49IkNOVF9DSElMRFJFTiIpDQpRMV9DaGlsZCA8LSBxdWFudGlsZSh0cmFpbmluZ1NldCRDTlRfQ0hJTERSRU4sIC4yNSkNClEzX0NoaWxkIDwtIHF1YW50aWxlKHRyYWluaW5nU2V0JENOVF9DSElMRFJFTiwgLjc1KQ0KSVFSX0NoaWxkIDwtIElRUih0cmFpbmluZ1NldCRDTlRfQ0hJTERSRU4pDQojIE5vdyB3ZSBrZWVwIHRoZSB2YWx1ZXMgd2l0aGluIDEuNSpJUVIgb2YgUTEgYW5kIFEzDQp0cmFpbmluZ1NldCA8LSBzdWJzZXQodHJhaW5pbmdTZXQsIHRyYWluaW5nU2V0JENOVF9DSElMRFJFTiA+IChRMV9DaGlsZCAtIDEuNSpJUVJfQ2hpbGQpICYgdHJhaW5pbmdTZXQkQ05UX0NISUxEUkVOIDwgKFEzX0NoaWxkICsgMS41KklRUl9DaGlsZCkpDQpkaW0odHJhaW5pbmdTZXQpDQoNCiMgQU1UX0lOQ09NRV9UT1RBTA0KYm94cGxvdCh0cmFpbmluZ1NldCRBTVRfSU5DT01FX1RPVEFMLCBob3Jpem9udGFsPVRSVUUsIG1haW49IkFNVF9JTkNPTUVfVE9UQUwiKQ0KUTFfQUlUIDwtIHF1YW50aWxlKHRyYWluaW5nU2V0JEFNVF9JTkNPTUVfVE9UQUwsIC4yNSkNClEzX0FJVCA8LSBxdWFudGlsZSh0cmFpbmluZ1NldCRBTVRfSU5DT01FX1RPVEFMLCAuNzUpDQpJUVJfQUlUIDwtIElRUih0cmFpbmluZ1NldCRBTVRfSU5DT01FX1RPVEFMKQ0KIyBOb3cgd2Uga2VlcCB0aGUgdmFsdWVzIHdpdGhpbiAxLjUqSVFSIG9mIFExIGFuZCBRMw0KdHJhaW5pbmdTZXQgPC0gc3Vic2V0KHRyYWluaW5nU2V0LCB0cmFpbmluZ1NldCRBTVRfSU5DT01FX1RPVEFMID4gKFExX0FJVCAtIDEuNSpJUVJfQUlUKSAmIHRyYWluaW5nU2V0JEFNVF9JTkNPTUVfVE9UQUwgPCAoUTNfQUlUICsgMS41KklRUl9BSVQpKQ0KZGltKHRyYWluaW5nU2V0KQ0KYGBgDQoNCmBgYHtyIENyZWF0ZSBhIHJlY2lwZSBmb3IgcHJlcHJvYzJ9DQpzZXQuc2VlZCg1KQ0KcHJlcHJvY1JlY2lwZSA8LQ0KICByZWNpcGUoc3RhdHVzIH4uLCBkYXRhID0gZGF0YSkgJT4lDQogIHN0ZXBfZHVtbXkoYWxsX25vbWluYWwoKSwgLXN0YXR1cywgIG9uZV9ob3QgPSBUUlVFKSAlPiUNCiAgc3RlcF9yYW5nZShhbGxfcHJlZGljdG9ycygpLCAtYWxsX25vbWluYWwoKSwgbWluID0gMCwgbWF4ID0gMSklPiUNCiAjIHN0ZXBfZG93bnNhbXBsZShzdGF0dXMsIG92ZXJfcmF0aW8gPSAxKSAlPiUNCiAgc3RlcF9zbW90ZShzdGF0dXMsIG92ZXJfcmF0aW8gPSAxLCBza2lwPVRSVUUpICU+JQ0KICMgc3RlcF9zbW90ZW5jKHN0YXR1cywgb3Zlcl9yYXRpbyA9IDEpICU+JQ0KICNzdGVwX2FkYXN5bihzdGF0dXMsIG92ZXJfcmF0aW8gPSAxKSAlPiUNCiAjc3RlcF9uZWFybWlzcyhzdGF0dXMsIG92ZXJfcmF0aW8gPSAxKSAlPiUNCiAgIA0KICBzdGVwX2R1bW15KHN0YXR1cywgIG9uZV9ob3QgPSBUUlVFKSMgJT4lDQpgYGANCg0KIyBJbiB0aGlzIHN0ZXAgdGhlIGFib3ZlIGRlZmluZWQgcmVjZWlwdCBpcyBleHRyYWN0ZWQgdXNpbmcgdGhlIGBwcmVwKClgIGZ1bmN0aW9uLCBhbmQgdGhlbiB1c2UgdGhlIGBiYWtlKClgIGZ1bmN0aW9uIHRvIHRyYW5zZm9ybSBhIHNldCBvZiBkYXRhIGJhc2VkIG9uIHRoYXQgcmVjaXBlLg0KYGBge3IgUHJlcCBhbmQgYmFrZSB0aGUgZGVmaW5lZCByZWNpcGV9DQojIHJldGFpbiA9IFRSVUUgYW5kIG5ld19kYXRhID0gTlVMTCBlbnN1cmVzIHRoYXQgcHJlLXByb2Nlc3NlZCB0cmFpbmluZ1NldCBpcyByZXR1cm5lZCANCnRyYWluaW5nU2V0X3Byb2Nlc3NlZCA8LSBwcmVwcm9jUmVjaXBlICU+JQ0KICBwcmVwKHRyYWluaW5nU2V0LCByZXRhaW4gPSBUUlVFKSAlPiUNCiAgYmFrZShuZXdfZGF0YSA9IE5VTEwpDQp0ZXN0U2V0X3Byb2Nlc3NlZCA8LSBwcmVwcm9jUmVjaXBlICU+JQ0KICBwcmVwKHRlc3RTZXQpICU+JQ0KICBiYWtlKG5ld19kYXRhID10ZXN0U2V0KQ0KDQojc3VtbWFyeSh0cmFpbmluZ1NldF9wcm9jZXNzZWQpDQpgYGANCg0KIyMgQ2hlY2sgZGF0YQ0KYGBge3J9DQojIHN1bW1hcml6ZSB0aGUgY2xhc3MgZGlzdHJpYnV0aW9uDQpwZXJjZW50YWdlIDwtIDEwMC1wcm9wLnRhYmxlKHRhYmxlKGRhdGEkc3RhdHVzKSkgKiAxMDANCmNiaW5kKGZyZXE9dGFibGUoZGF0YSRzdGF0dXMpLCBwZXJjZW50YWdlPXBlcmNlbnRhZ2UpDQpjbGFzc193ZWlnaHRzIDwtIGxpc3QoIjAiPTEsIjEiPTEwMCkNCg0KIyBUdXJuIGRhdGEgZnJhbWUgaW50byBkYXRhIG1hdHJpeA0KbWF0cml4X2RhdGEgPC0gdHJhaW5pbmdTZXRfcHJvY2Vzc2VkICU+JSBzZWxlY3QoLXRhaWwobmFtZXModHJhaW5pbmdTZXRfcHJvY2Vzc2VkKSwgOCkpDQptYXRyaXhfdGFyZ2V0cyA8LSB0cmFpbmluZ1NldF9wcm9jZXNzZWQgJT4lIHNlbGVjdCh0YWlsKG5hbWVzKHRyYWluaW5nU2V0X3Byb2Nlc3NlZCksIDgpKQ0KDQptYXRyaXhfZGF0YV90ZXN0ICA8LSB0ZXN0U2V0X3Byb2Nlc3NlZCAlPiUgc2VsZWN0KC10YWlsKG5hbWVzKHRlc3RTZXRfcHJvY2Vzc2VkKSwgOCkpDQptYXRyaXhfdGFyZ2V0c190ZXN0ICA8LSB0ZXN0U2V0X3Byb2Nlc3NlZCAlPiUgc2VsZWN0KHRhaWwobmFtZXModGVzdFNldF9wcm9jZXNzZWQpLCA4KSkNCg0KI1N1YnNldCBvbmx5IDEwMCBlbnRyaWVzIGZvciB0ZXN0aW5nDQojbWF0cml4X2RhdGEgPC0gbWF0cml4X2RhdGFbMToxMDAsIF0NCiNtYXRyaXhfdGFyZ2V0cyA8LSBtYXRyaXhfdGFyZ2V0c1sxOjEwMCwgXQ0KYGBgDQojIyBCdWlsZCBNb2RlbA0KYGBge3J9DQojdHJhaW5fZGF0YSA8LSBtYXRyaXhfZGF0YQ0KdHJhaW5fZGF0YSA8LSBkYXRhLm1hdHJpeChtYXRyaXhfZGF0YSkNCnRlc3RfZGF0YSA8LSBkYXRhLm1hdHJpeChtYXRyaXhfZGF0YV90ZXN0KQ0KdHJhaW5fdGFyZ2V0cyA8LSBkYXRhLm1hdHJpeChtYXRyaXhfdGFyZ2V0cykNCnRlc3RfdGFyZ2V0cyA8LSBkYXRhLm1hdHJpeChtYXRyaXhfdGFyZ2V0c190ZXN0KQ0KDQojIEZ1bmN0aW9uIHRvIGJ1aWxkIHRoZSBtb2RlbA0KYnVpbGRfbW9kZWwgPC0gZnVuY3Rpb24oKSB7DQogIG1vZGVsIDwtIGtlcmFzX21vZGVsX3NlcXVlbnRpYWwoKSAlPiUNCiAgICAjbGF5ZXJfYmF0Y2hfbm9ybWFsaXphdGlvbihheGlzID0gLTFMLCBpbnB1dF9zaGFwZSA9IGRpbSh0cmFpbl9kYXRhKVtbMl1dKSAlPiUNCiAgICBsYXllcl9kZW5zZSh1bml0cyA9IDY0LCBhY3RpdmF0aW9uID0gInJlbHUiLCBpbnB1dF9zaGFwZSA9IGRpbSh0cmFpbl9kYXRhKVtbMl1dKSAlPiUNCiAgICBsYXllcl9kcm9wb3V0KDAuMykgJT4lDQogICAgbGF5ZXJfZGVuc2UodW5pdHMgPSA2NCwgYWN0aXZhdGlvbiA9ICJyZWx1IikgJT4lDQogICAgbGF5ZXJfZHJvcG91dCgwLjMpICU+JQ0KICAgIGxheWVyX2RlbnNlKHVuaXRzID0gOCwgYWN0aXZhdGlvbiA9ICJzb2Z0bWF4IikgDQoNCiAgbW9kZWwgJT4lIGNvbXBpbGUoDQogICAgb3B0aW1pemVyID0gb3B0aW1pemVyX3NnZChsZWFybmluZ19yYXRlID0gMC4yKSwNCiAgICBsb3NzID0gImNhdGVnb3JpY2FsX2Nyb3NzZW50cm9weSIsDQogICAgbWV0cmljcyA9ICJjYXRlZ29yaWNhbF9hY2N1cmFjeSINCiAgKQ0KDQp9DQpgYGANCiMjIEstRm9sZC1WYWxpZGF0aW9uDQpgYGB7cn0NCiMgbWVhbiA8LSBhcHBseShtYXRyaXhfZGF0YSwgMiwgbWVhbikNCiMgc3RkIDwtIGFwcGx5KG1hdHJpeF9kYXRhLCAyLCBzZCkNCiMgdHJhaW5fZGF0YSA8LSBzY2FsZShtYXRyaXhfZGF0YSwgY2VudGVyID0gbWVhbiwgc2NhbGUgPSBzdGQpDQojIHRlc3RfZGF0YSA8LSBzY2FsZShtYXRyaXhfZGF0YSwgY2VudGVyID0gbWVhbiwgc2NhbGUgPSBzdGQpDQojIHRyYWluX3RhcmdldHMgPC0gbWF0cml4X3RhcmdldHMNCg0KDQprIDwtIDEwDQppbmRpY2VzIDwtIHNhbXBsZSgxOm5yb3codHJhaW5fZGF0YSkpDQpmb2xkcyA8LSBjdXQoaW5kaWNlcywgYnJlYWtzID0gaywgbGFiZWxzID0gRkFMU0UpDQoNCm51bV9lcG9jaHMgPC0gMTUwMA0KYWxsX2FjY19oaXN0b3JpZXMgPC0gTlVMTA0KZm9yIChpIGluIDE6aykgew0KICBjYXQoInByb2Nlc3NpbmcgZm9sZCAjIiwgaSwgIlxuIikNCg0KICB2YWxfaW5kaWNlcyA8LSB3aGljaChmb2xkcyA9PSBpLCBhcnIuaW5kID0gVFJVRSkNCiAgdmFsX2RhdGEgPC0gdHJhaW5fZGF0YVt2YWxfaW5kaWNlcyxdICN0ZXN0X2RhdGEjDQogIHZhbF90YXJnZXRzIDwtIHRyYWluX3RhcmdldHNbdmFsX2luZGljZXMsXSAjdGVzdF90YXJnZXRzIw0KICANCiAgcGFydGlhbF90cmFpbl9kYXRhIDwtIHRyYWluX2RhdGFbLXZhbF9pbmRpY2VzLF0NCiAgcGFydGlhbF90cmFpbl90YXJnZXRzIDwtIHRyYWluX3RhcmdldHNbLXZhbF9pbmRpY2VzLF0NCiAgbW9kZWwgPC0gYnVpbGRfbW9kZWwoKQ0KDQogICMgVHJhaW4gdGhlIG1vZGVsIChpbiBzaWxlbnQgbW9kZSwgdmVyYm9zZT0wKQ0KICAjIEJhdGNoIHNpemUgaHR0cHM6Ly9zdGF0cy5zdGFja2V4Y2hhbmdlLmNvbS9xdWVzdGlvbnMvMTUzNTMxL3doYXQtaXMtYmF0Y2gtc2l6ZS1pbi1uZXVyYWwtbmV0d29yaw0KICAjIE9uZSBlcG9jaCA9IG9uZSBmb3J3YXJkIHBhc3MgYW5kIG9uZSBiYWNrd2FyZCBwYXNzIG9mIGFsbCB0aGUgdHJhaW5pbmcgZXhhbXBsZXMNCiAgIyBCYXRjaCBzaXplID0gdGhlIG51bWJlciBvZiB0cmFpbmluZyBleGFtcGxlcyBpbiBvbmUgZm9yd2FyZC9iYWNrd2FyZCBwYXNzLiBUaGUgaGlnaGVyIHRoZSBiYXRjaCBzaXplLCB0aGUgbW9yZSBtZW1vcnkgc3BhY2UgeW91J2xsIG5lZWQuDQogICMgTnVtYmVyIG9mIGl0ZXJhdGlvbnMgPSBudW1iZXIgb2YgcGFzc2VzLCBlYWNoIHBhc3MgdXNpbmcgW2JhdGNoIHNpemVdIG51bWJlciBvZiBleGFtcGxlcy4gVG8gYmUgY2xlYXIsIG9uZSBwYXNzID0gb25lIGZvcndhcmQgcGFzcyArIG9uZSBiYWNrd2FyZCBwYXNzICh3ZSBkbyBub3QgY291bnQgdGhlIGZvcndhcmQgcGFzcyBhbmQgYmFja3dhcmQgcGFzcyBhcyB0d28gZGlmZmVyZW50IHBhc3NlcykuDQogICMgQmF0Y2ggc2l6ZSAzMiBtdWNoIGZhc3RlciB0aGFuIDEsIGFsc28gdGhlIHNtYWxsZXIgdGhlIGJhdGNoIHRoZSBsZXNzIGFjY3VyYXRlIHRoZSBlc3RpbWF0ZSBvZiB0aGUgZ3JhZGllbnQgd2lsbCBiZS4NCiAgaGlzdG9yeSA8LSBtb2RlbCAlPiUgZml0KA0KICAgIHBhcnRpYWxfdHJhaW5fZGF0YSwgcGFydGlhbF90cmFpbl90YXJnZXRzLA0KICAgIHZhbGlkYXRpb25fZGF0YSA9IGxpc3QodmFsX2RhdGEsIHZhbF90YXJnZXRzKSwNCiAgICBlcG9jaHMgPSBudW1fZXBvY2hzLCBiYXRjaF9zaXplID0gMTI4LCB2ZXJib3NlID0gMiwgY2xhc3Nfd2VpZ2h0cyA9IHBlcmNlbnRhZ2UNCiAgKQ0KICBhY2NfaGlzdG9yeSA8LSBoaXN0b3J5JG1ldHJpY3MkdmFsX2NhdGVnb3JpY2FsX2FjY3VyYWN5DQogIGFsbF9hY2NfaGlzdG9yaWVzIDwtIHJiaW5kKGFsbF9hY2NfaGlzdG9yaWVzLCBhY2NfaGlzdG9yeSkNCn0NCg0KDQojcmV0aWN1bGF0ZTo6cHlfbGFzdF9lcnJvcigpDQpgYGANCg0KI1dlIGNhbiB0aGVuIGNvbXB1dGUgdGhlIGF2ZXJhZ2Ugb2YgdGhlIHBlci1lcG9jaCBBQ0Mgc2NvcmVzIGZvciBhbGwgZm9sZHM6DQoNCmBgYHtyfQ0KYXZlcmFnZV9hY2NfaGlzdG9yeSA8LSBkYXRhLmZyYW1lKA0KICBlcG9jaCA9IHNlcSgxOm5jb2woYWxsX2FjY19oaXN0b3JpZXMpKSwNCiAgdmFsaWRhdGlvbl9hY2MgPSBhcHBseShhbGxfYWNjX2hpc3RvcmllcywgMiwgbWVhbikNCikNCg0KDQptYXgoYXZlcmFnZV9hY2NfaGlzdG9yeSR2YWxpZGF0aW9uX2FjYykNCg0KbGlicmFyeShnZ3Bsb3QyKQ0KZ2dwbG90KGF2ZXJhZ2VfYWNjX2hpc3RvcnksIGFlcyh4ID0gZXBvY2gsIHkgPSB2YWxpZGF0aW9uX2FjYykpICsgZ2VvbV9saW5lKCkNCg0KI0l0IG1heSBiZSBhIGJpdCBoYXJkIHRvIHNlZSB0aGUgcGxvdCBkdWUgdG8gc2NhbGluZyBpc3N1ZXMgYW5kIHJlbGF0aXZlbHkgaGlnaCB2YXJpYW5jZS4gTGV0J3MgdXNlIGBnZW9tX3Ntb290aCgpYCB0byB0cnkgdG8gZ2V0IGEgY2xlYXJlciBwaWN0dXJlOg0KZ2dwbG90KGF2ZXJhZ2VfYWNjX2hpc3RvcnksIGFlcyh4ID0gZXBvY2gsIHkgPSB2YWxpZGF0aW9uX2FjYykpICsgZ2VvbV9zbW9vdGgoKQ0KDQojIEV2YWx1YXRlIG9uIFRlc3RzZXQNCmV2YWwgPC0gZXZhbHVhdGUobW9kZWwsIHRlc3RfZGF0YSwgdGVzdF90YXJnZXRzLCB2ZXJib3NlID0gMSkNCmV2YWwNCg0KIyBTYXZlIG1vZGVsIGFuZCBoaXN0b3J5LCBwbGVhc2UgY2hhbmdlIHRoZSBuYW1lDQojIHdyaXRlLmNzdihhdmVyYWdlX2FjY19oaXN0b3J5LCAiLi4vRG9jL1ZlcnN1Y2ggMy9UcnkgMy5jc3YiLCByb3cubmFtZXM9RkFMU0UpDQojIHNhdmVfbW9kZWxfaGRmNShtb2RlbCwgIi4uL0RvYy9WZXJzdWNoIDMvbW9kZWwgMy5oZmQ1Iiwgb3ZlcndyaXRlID0gVFJVRSwgaW5jbHVkZV9vcHRpbWl6ZXIgPSBUUlVFKQ0KDQojIExvYWQgbW9kZWwNCiMgVXNlIG1vZGVsX2hpc3RvcnkgYXMgcHJlY2F1dGlvbg0KIyBtb2RlbF9oaXN0b3J5IDwtIGxvYWRfbW9kZWxfaGRmNSgiLi4vRG9jL1ZlcnN1Y2ggMy9tb2RlbCAzLmhmZDUiLCBjdXN0b21fb2JqZWN0cyA9IE5VTEwsIGNvbXBpbGUgPSBUUlVFKQ0KDQpgYGA=